package models

import (
	"crypto/hmac"
	"crypto/sha256"
	"errors"
	"io"

	"crypto/tls"
	"encoding/xml"
	"fmt"
	mrand "math/rand"
	"sort"
	"strings"
	"time"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/httplib"
	"github.com/astaxie/beego/orm"
	_ "github.com/go-sql-driver/mysql"
)

const (
	order_status_create     = "create"
	order_status_call       = "call"
	order_status_service    = "service"
	order_status_wait_pay   = "needPay"
	order_status_wait_score = "payDone"
	order_status_finish     = "finish"
	order_status_cancel     = "cancel"
	wechatPaymentUrl        = "https://api.mch.weixin.qq.com/pay/unifiedorder"
	order_mch_id            = "1503283311"
	order_appid             = "wxa4dedfcf2df51494"
	order_key               = "hkXrbQJqUT1RdM7uQCRg7oT0yUHmzrzT"
	paymentCallUrl          = "https://www.i-hea.com/v1/main/payCallback"
)

type Order struct {
	Id            int        `orm:"column(Id)"`
	Type          string     `orm:"column(sType)"`  //active,store
	Status        string     `orm:"column(Status)"` //create 创建；call 叫号;service 服务中 ；needPay 服务完成，待支付；payDone 支付完成，待评论；finish
	Cost          int32      `orm:"column(Cost)"`
	Product       string     `orm:"null;column(Product)"`
	PayMethord    string     `orm:"null;column(PayMethord)"` //wechat
	PayTime       time.Time  `orm:"null;column(PayTime)"`
	ServeTime     time.Time  `orm:"null;column(ServeTime)"`
	ServeDoneTime time.Time  `orm:"null;column(ServeDoneTime)"`
	OrderStaff    int        `orm:"column(OrderStaff)"`
	ServeStaff    int        `orm:"null;column(ServeStaff)"`
	RelationId    int        `orm:"null;column(RelationId)"`
	UserId        int        `orm:"null;column(UserId)"`
	CreateTime    time.Time  `orm:"column(CreateTime)"`
	Score         int        `orm:"null;column(Score)"`
	ScoreTime     time.Time  `orm:"null;column(ScoreTime)"`
	Comment       string     `orm:"null;column(Comment)"`
	TradeNo       string     `orm:"null;column(TradeNo)"`
	TransactionId string     `orm:"null;column(TransactionId)"`
	PayEndTime    time.Time  `orm:"null;column(PayEndTime)"`
	Store         *StoreInfo `orm:"null;rel(fk);on_delete(set_null)"`
	Active        *Active    `orm:"null;rel(fk);on_delete(set_null)"`
	Service       *Statff    `orm:"null;rel(fk);on_delete(set_null)"`
}

func (p *Order) TableName() string {
	return "Order"
}

func AddNewOrder(userId, storeId, staffId int, product string) (int64, error) {

	o := orm.NewOrm()
	o.Using("default")

	store := StoreInfo{
		Id: storeId,
	}

	newOrder := Order{
		Status:     order_status_create,
		Product:    product,
		CreateTime: time.Now(),
		OrderStaff: staffId,
		RelationId: storeId,
		UserId:     userId,
		Type:       "store",
		Store:      &store,
	}

	id, err := o.Insert(&newOrder)
	if nil != err {
		beego.Error("insert order fail ", err)
	}

	return id, err
}

func AddNewOrderByActive(userId, activeId int, product string) (int64, error) {

	o := orm.NewOrm()
	o.Using("default")

	active := Active{
		Id: activeId,
	}

	newOrder := Order{
		Status:     order_status_create,
		Product:    product,
		CreateTime: time.Now(),
		RelationId: activeId,
		UserId:     userId,
		Type:       "active",
		Active:     &active,
	}

	id, err := o.Insert(&newOrder)
	if nil != err {
		beego.Error("insert order fail ", err)
	}

	return id, err
}

func StartServe(orderId int) error {

	newOrder := Order{
		Id:        orderId,
		ServeTime: time.Now(),
		Status:    order_status_service,
	}

	o := orm.NewOrm()
	o.Using("default")

	_, err := o.Update(&newOrder, "ServTime", "Status")
	return err
}

func CallServe(orderId int, staff *Statff) error {

	newOrder := Order{
		Id:         orderId,
		ServeStaff: staff.Id,
		Status:     order_status_call,
		Service:    staff,
	}

	o := orm.NewOrm()
	o.Using("default")

	_, err := o.Update(&newOrder, "ServeStaff", "Status", "Service")
	return err
}

func ServeDone(orderId int, cost int32) error {

	newOrder := Order{
		Id:            orderId,
		Cost:          cost,
		ServeDoneTime: time.Now(),
		Status:        order_status_wait_pay,
	}

	o := orm.NewOrm()
	o.Using("default")

	_, err := o.Update(&newOrder, "Cost", "ServeDoneTime", "Status")
	return err
}

func AddScore(orderId int, score int, comment string) error {

	newOrder := Order{
		Id:        orderId,
		Score:     score,
		ScoreTime: time.Now(),
		Status:    order_status_finish,
		Comment:   comment,
	}

	o := orm.NewOrm()
	o.Using("default")

	_, err := o.Update(&newOrder, "Score", "ScoreTime", "Status", "Comment")
	return err
}

func CancelOrder(orderId int, userId int) error {

	newOrder := Order{
		Id:     orderId,
		UserId: userId,
	}

	o := orm.NewOrm()
	o.Using("default")

	err := o.Read(&newOrder, "Id", "UserId")
	if nil != err {
		return err
	}

	if newOrder.Status == order_status_create || newOrder.Status == order_status_call {

		newOrder.Status = order_status_cancel
		_, err = o.Update(&newOrder, "Status")
	} else {
		err = errors.New("cannot cancel the order")
	}

	return err
}

func OrderFinishPay(orderId, payCost int, transactionId, time_end, tradeNo string) error {

	newOrder := Order{
		Id:      orderId,
		Cost:    int32(payCost),
		TradeNo: tradeNo,
	}

	o := orm.NewOrm()
	o.Using("default")

	err := o.Read(&newOrder, "Id", "Cost", "TradeNo")
	if nil != err {
		beego.Error("get order info failed:", err)
		return err
	}

	loc, _ := time.LoadLocation("Local")                                           //重要：获取时区
	newOrder.PayEndTime, _ = time.ParseInLocation("20060102150405", time_end, loc) //使用模板在对应时区转化为time.time类型
	newOrder.TransactionId = transactionId
	newOrder.Status = order_status_wait_score

	_, err = o.Update(newOrder)
	if err != nil {
		beego.Error("update failed:", err)
	}

	return err
}

type OrderJson struct {
	OrderId    int    `json:"OrderId"`
	StoreName  string `json:"StoreName"`
	StoreAddr  string `json:"StoreAddr"`
	Status     string `json:"status"`
	ServeTime  string `json:"ServeTime"`
	Staff      string `json:"Staff"`
	PayMethord string `json:"PayMethord"`
	Product    string `json:"Product"`
	Cost       int32  `json:"Cost"`
	Type       string `json:"Type"`
}

func encodeOrders(orders []*Order) []OrderJson {

	var result []OrderJson
	for _, v := range orders {

		var status string
		switch v.Status {
		case order_status_create:
			status = "排队中"
		case order_status_call:
			status = "已叫号"
		case order_status_service:
			status = "服务中"
		case order_status_wait_pay:
			status = "去付款"
		case order_status_wait_score:
			status = "去评价"
		case order_status_finish:
			status = "已评价"
		case order_status_cancel:
			status = "已取消"
		default:
			status = "错误订单"
		}

		resOne := OrderJson{
			OrderId:    v.Id,
			Status:     status,
			ServeTime:  v.ServeTime.Format("2006-01-02 15:04:05"),
			Staff:      v.Service.NickName,
			PayMethord: v.PayMethord,
			Product:    v.Product,
			Cost:       v.Cost,
			Type:       v.Type,
		}

		if v.Type == "active" {
			resOne.StoreName = v.Active.Title
			resOne.StoreAddr = v.Active.Address
		} else {
			resOne.StoreName = v.Store.Name
			resOne.StoreAddr = v.Store.Addr
		}

		result = append(result, resOne)
	}
	return result
}

func QueryOrdersByType(id int, orderType string) (int, []OrderJson, error) {

	var orders []*Order

	o := orm.NewOrm()
	o.Using("default")

	order := new(Order)
	acs := o.QueryTable(order)

	var err error
	var number int64

	if orderType == "active" {
		number, err = acs.OrderBy("-CreateTime").Filter("Active", id).Limit(50).All(&orders)
	} else {
		number, err = acs.OrderBy("-CreateTime").Filter("Store", id).Limit(50).All(&orders)
	}

	if err != nil {
		beego.Error("query failed:", err)
		return 0, nil, err
	}

	result := encodeOrders(orders)
	return int(number), result, nil

}

func QueryOrdersByUserId(userId int) (int, []OrderJson, error) {

	var orders []*Order

	o := orm.NewOrm()
	o.Using("default")

	order := new(Order)
	acs := o.QueryTable(order)
	num, err := acs.OrderBy("-CreateTime").Filter("UserId", "userId").Limit(50).All(&orders)
	if err != nil {
		beego.Error("acs orders failed!", err)
		return 0, nil, err
	}

	return int(num), encodeOrders(orders), nil
}

type payment struct {
	Appid            string `xml:"appid"`
	Mch_id           string `xml:"mch_id"` //微信支付分配的商户号
	Device_info      string `xml:"device_info"`
	Nonce_str        string `xml:"nonce_str"`
	Sign             string `xml:"sign"`
	Sign_type        string `xml:"sign_type"`
	Body             string `xml:"body"`
	Detail           string `xml:"detail"`
	Attach           string `xml:"attach"`
	Out_trade_no     string `xml:"out_trade_no"`
	Fee_type         string `xml:"fee_type"`
	Total_fee        int32  `xml:"total_fee"`
	Spbill_create_ip string `xml:"spbill_create_ip"`
	Time_start       string `xml:"time_start"`  //yyyyMMddHHmmss
	Time_expire      string `xml:"time_expire"` //20091227091010
	Goods_tag        string `xml:"goods_tag"`   //订单优惠标记，使用代金券或立减优惠功能时需要的参数，说明详见代金券或立减优惠
	Notify_url       string `xml:"notify_url"`  //异步接收微信支付结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。
	Trade_type       string `xml:"trade_type"`  //JSAPI
	Product_id       string `xml:"product_id"`  //12235413214070356458058
	Limit_pay        string `xml:"limit_pay"`   //上传此参数no_credit--可限制用户不能使用信用卡支付
	OpenId           string `xml:"openid"`
}

func getRandomString(lenth int64) string {

	str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	result := ""
	r := mrand.New(mrand.NewSource(time.Now().UnixNano()))
	var i int64
	for i = 0; i < lenth; i++ {
		result += string(str[r.Intn(len(str))])
	}
	return (result)
}

func getHmacCode(s string) string {
	h := hmac.New(sha256.New, []byte("hkXrbQJqUT1RdM7uQCRg7oT0yUHmzrzT"))
	io.WriteString(h, s)
	return fmt.Sprintf("%x", h.Sum(nil))
}
func wechatSign(inputParam []param) string {

	var signStr string

	sort.Sort(paramSlice(inputParam))

	for _, value := range inputParam {
		signStr += value.key + "=" + value.value + "&"
	}

	signStr += "key=" + order_key

	beego.Debug("param:", signStr)

	return strings.ToUpper(getHmacCode(signStr))
}

func getOrder(orderId int) (*Order, error) {

	o := orm.NewOrm()
	o.Using("default")

	order := Order{
		Id: orderId,
	}

	err := o.Read(&order)
	return &order, err
}

func (this *Order) upOrderTradeNo(tradeNo string) error {

	o := orm.NewOrm()
	o.Using("default")

	this.TradeNo = tradeNo
	_, err := o.Update(this, "TradeNo")

	return err
}

func PaymentRequestToWepay(openId, ip string, orderId int) error {

	order, err := getOrder(orderId)
	if err != nil {
		beego.Error("cannot get order info ", err)
		return errors.New("order is not exist")
	}

	request := payment{
		OpenId:           openId,
		Detail:           order.Product,
		Total_fee:        order.Cost,
		Appid:            order_appid,
		Mch_id:           order_mch_id,
		Nonce_str:        getRandomString(32),
		Body:             "小姐姐美容-" + order.Product,
		Out_trade_no:     SessIonCreate.NextId(),
		Fee_type:         "CNY",
		Spbill_create_ip: ip,
		Time_start:       time.Now().Format("20060102150405"),
		Time_expire:      time.Now().Add(1 * time.Hour).Format("20060102150405"),
		Notify_url:       paymentCallUrl,
		Trade_type:       "JSAPI",
		Sign_type:        "HMAC-SHA256",
		Attach:           fmt.Sprint("%d", orderId),
		//Limit_pay:        "",
		//Goods_tag:        "",
		//Sign:             "",
	}

	sign := newParamList()

	sign.addParam("appid", request.Appid)
	sign.addParam("attach", request.Attach)
	sign.addParam("body", request.Body)
	sign.addParam("detail", request.Detail)
	//sign.addParam("device_info", request.Device_info)
	sign.addParam("fee_type", request.Fee_type)
	//sign.addParam("goods_tag", request.Goods_tag)
	//sign.addParam("limit_pay", request.Limit_pay)
	sign.addParam("mch_id", request.Mch_id)
	sign.addParam("nonce_str", request.Nonce_str)
	sign.addParam("notify_url", request.Notify_url)
	sign.addParam("openid", request.OpenId)
	sign.addParam("out_trade_no", request.Out_trade_no)
	//sign.addParam("product_id", request.Product_id)
	sign.addParam("sign_type", request.Sign_type)
	sign.addParam("spbill_create_ip", request.Spbill_create_ip)
	sign.addParam("time_expire", request.Time_expire)
	sign.addParam("time_start", request.Time_start)
	sign.addParam("total_fee", fmt.Sprintf("%d", request.Total_fee))
	sign.addParam("trade_type", request.Trade_type)

	request.Sign = wechatSign(sign)

	requstBody, err := xml.Marshal(request)
	if err != nil {
		beego.Error("xml encode failed:", err)
		return errors.New("系统错误，请重试")
	}

	order.upOrderTradeNo(request.Out_trade_no)

	//beego.Debug("send :", string(requstBody))
	req := httplib.Post(wechatPaymentUrl)
	req.Body(requstBody).SetTimeout(5*time.Second, 5*time.Second)
	req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})

	response, err := req.Bytes()
	if err != nil {
		beego.Error("request to wx failed")
		return errors.New("支付失败，请重试")
	}

	result := payResponse{}

	err = xml.Unmarshal(response, &result)
	if err != nil {
		beego.Error("request to wx failed", string(response))
		return errors.New("微信支付失败,请使用其他方式支付")
	}

	if result.ResultCode != "SUCCESS" {
		beego.Error("prepay faied,", string(response))
		return errors.New("微信支付失败,请使用其他方式支付")
	}

	return nil
}

type payResponse struct {
	ReturnCode string `xml:"return_code"`
	ReturnMsg  string `xml:"return_msg"`
	Appid      string `xml:"appid"`
	MchId      string `xml:"mch_id"`
	NonceStr   string `xml:"nonce_str"`
	Sign       string `xml:"sign"`
	ResultCode string `xml:"result_code"`
	PrepayId   string `xml:"prepay_id"`
}
